//(function () {
// 游戏配置
let difficulty = {}


let config = {

    easy: {
        INIT_SCORE: 120,
        // Initial score is 120;
        HERO_LOAD_CAPACITY: 50,
        // Missile load capacity for the hero is 50;
        ENEMY_COUNT_MAX: 10,
        // No more than 10 enemy fighters on the screen;
        ENEMY_MISSILE_COUNT_MAX: 1,
        // No more than 1 enemy missile on the screen;
        ENERGY_CAN_COUNT_MAX: 3,
        // No more than 3 energy cans on the screen;
        AMMO_BOX_COUNT_MAX: 3,
        // No more than 3 ammo boxes on the screen;
        COLLIDED_BY_ENEMY_COST_H: 10,
        // Collided by an enemy fighter lose 10% of healthiness;
        CATCH_ENERGY_CAN_GAIN_H: 10,
        // Catching an energy can gets 10% of healthiness;
        CATCH_AMMO_BOX_GAIN_M: 10,
        // Catching an ammo box gets 10 missiles;
        DESTROY_ENEMY_GAIN_S: 3,
        // Destroying an enemy fighter get 3 points of score;
        DESTROY_ENERGY_CAN_COST_S: 1,
        // Destroying an energy can loses 1 point of score;
        DESTROY_AMMO_BOX_COST_S: 1,
        // Destroying an ammo box loses 1 point of score;
        ESCAPING_OF_ENEMY_COST_S: 1,
        // 1 point of score lost for an enemy fighter escaping;
        SHOT_BY_ENEMY_COST_H: 5,
        // Shot by enemy missile loses 5% of healthiness;
    },
    medium: {
        INIT_SCORE: 80,
        // Initial score is 80;
        HERO_LOAD_CAPACITY: 30,
        // Missile load capacity for the hero is 30;
        ENEMY_COUNT_MAX: 20,
        // No more than 20 enemy fighters on the screen;
        ENEMY_MISSILE_COUNT_MAX: 3,
        // No more than 3 enemy missiles on the screen;
        ENERGY_CAN_COUNT_MAX: 2,
        // No more than 2 energy cans on the screen;
        AMMO_BOX_COUNT_MAX: 2,
        // No more than 2 ammo boxes on the screen;
        COLLIDED_BY_ENEMY_COST_H: 20,
        // Collided by an enemy fighter lose 20% of healthiness;
        CATCH_ENERGY_CAN_GAIN_H: 10,
        // Catching an energy can gets 10% of healthiness;
        CATCH_AMMO_BOX_GAIN_M: 8,
        // Catching an ammo box gets 8 missiles;
        DESTROY_ENEMY_GAIN_S: 2,
        // Destroying an enemy fighter get 2 points of score;
        DESTROY_ENERGY_CAN_COST_S: 2,
        // Destroying an energy can loses 2 points of score;
        DESTROY_AMMO_BOX_COST_S: 2,
        // Destroying an ammo box loses 2 points of score;
        ESCAPING_OF_ENEMY_COST_S: 1,
        // 1 point of score lost for an enemy fighter escaping;
        SHOT_BY_ENEMY_COST_H: 10,
        // Shot by enemy missile loses 10% of healthiness;
    },
    hard: {
        INIT_SCORE: 50,
        // Initial score is 50;
        HERO_LOAD_CAPACITY: 20,
        // Missile load capacity for the hero is 20;
        ENEMY_COUNT_MAX: 20,
        // No more than 20 enemy fighters on the screen;
        ENEMY_MISSILE_COUNT_MAX: 5,
        // No more than 5 enemy missiles on the screen;
        ENERGY_CAN_COUNT_MAX: 2,
        // No more than 2 energy cans on the screen;
        AMMO_BOX_COUNT_MAX: 2,
        // No more than 2 ammo boxes on the screen;
        COLLIDED_BY_ENEMY_COST_H: 30,
        // Collided by an enemy fighter lose 30% of healthiness;
        CATCH_ENERGY_CAN_GAIN_H: 5,
        // Catching an energy can gets 5% of healthiness;
        CATCH_AMMO_BOX_GAIN_M: 5,
        // Catching an ammo box gets 5 missiles;
        DESTROY_ENEMY_GAIN_S: 1,
        // Destroying an enemy fighter get 1 point of score;
        DESTROY_ENERGY_CAN_COST_S: 3,
        // Destroying an energy can loses 3 points of score;
        DESTROY_AMMO_BOX_COST_S: 3,
        // Destroying an ammo box loses 3 points of score;
        ESCAPING_OF_ENEMY_COST_S: 1,
        // 1 point of score lost for an enemy fighter escaping;
        SHOT_BY_ENEMY_COST_H: 15,
        // Shot by enemy missile loses 15% of healthiness;
    },
    GAME_DATA_KEY: "space_fighting_data"
};

/** 战场范围 */
let Field = {
    width: 1200,
    height: 800
};
// 定义全局变量
let explodes_at = [];

// 定义全局函数
/** 绘制爆炸效果 */
async function drawExplosion() {
    for (let i = 0; i < explodes_at.length; i++) {
        Game.explodeAt(explodes_at[i].left, explodes_at[i].top)
        Game.sounds["explosion"].currentTime = 0;
        try{
            await Game.sounds["explosion"].play();
        }
        catch(err){

        }
    }
    explodes_at = [];
}

// 事件定义

let on_score_change = new CustomEvent("score_change", {
    detail: {}
});
let on_life_value_change = new CustomEvent("life_value_change", {
    detail: {}
});
let on_missile_count_change = new CustomEvent("missile_count_change", {
    detail: {}
});

// 事件监听

document.addEventListener("score_change", function (e) {
    Game.hero.tune_score(e.detail.value);
});

document.addEventListener("life_value_change", function (e) {
    Game.hero.tune_life_value(e.detail.value);
});
document.addEventListener("missile_count_change", function (e) {
    Game.hero.tune_missile_count(e.detail.value);
});

// 键盘按键按下
document.addEventListener("keydown", function (e) {
    if(Game.cur_screen==="start") return;
    if (Game.cur_screen === "battle-space") {
        if (e.key === "h" || e.key === "H") {
            Game.cur_screen = "help"
            H.show("#help")
            Game.start()
        } else if (e.key === "p" || e.key === "P") {
            Game.cur_screen = "paused"
            H.show("#paused")
            Game.start()
        } else if (e.key.indexOf("Arrow")===0) {
            // Arrow Keys
            Game.hero.moving = true;
            Game.hero.dir = e.key.replace("Arrow","").toLowerCase();
        } else if (e.key === " ") {
            Game.hero.fire();
        }
    } else if (Game.cur_screen === "help" && e.key === "h" || e.key === "H") {

        Game.cur_screen = "battle-space"
        H.hide("#help")
        Game.start()

    } else if (Game.cur_screen === "paused" && e.key === "p" || e.key === "P") {

        Game.cur_screen = "battle-space"
        H.hide("#paused")
        Game.start()
    }

});

// 键盘按键释放
document.addEventListener("keyup", function (e) {
    if(e.key===undefined) return
    if (e.key.indexOf("Arrow")!==-1) {
        Game.hero.moving = false;
        Game.hero.dir = "";
    }
    if (e.key === " ") {
        Game.hero.shooting = false;
    }
});

// 游戏开始画面 开始游戏按钮 点击
document
    .getElementById("start-game-button")
    .addEventListener("click", function (e) {
        // document.getElementById("start").style.display = "none";
        // document.getElementById("battle-space").style.display = "block";
        // Game.running = true
        H.hide("#start")
        H.show("#battle-space")
        Game.cur_screen = "battle-space"
        Game.start();
    });

// 游戏开始画面 玩家昵称输入框 按键释放
document.getElementById("player-name").addEventListener("keyup", function (e) {
    if (e.target.value != "") {
        if (Game.ready) {
            document.getElementById("start-game-button").disabled = false;
            Game.current_user = e.target.value;
            if (e.key == "Enter") {
                document.getElementById("start-game-button").click();
            }
        }
    } else {
        document.getElementById("start-game-button").disabled = true;
    }
});
// 游戏结束画面 重新开始按钮 点击
document
    .getElementById("restart-game-button")
    .addEventListener("click", function () {
        Game.init();
    });

// 游戏开始画面 选择游戏难度 难度改变
document.getElementById("difficulty").addEventListener("change", function (e) {
    Game.cfg = config[e.target.value];
    Game.difficulty = e.target.options[e.target.selectedIndex].text;
    Game.init();
});

// 游戏开始画面 清除游戏数据按钮 点击
document.getElementById("clear-data").addEventListener("click", function (e) {
    localStorage.removeItem(config.GAME_DATA_KEY);
    console.log(e.target);
    e.target.disabled = true;
});

// 相关对象与构造函数定义

/** 帮助对象 */
let H = {
    get_rand_drop_pos(obj_width, far_top = -3000) {
        return [
            Math.floor(Math.random() * (Field.width - obj_width)),
            Math.floor(Math.random() * far_top)
        ];
    },
    collided(obj1, obj2) {
        return (
            obj1.left < obj2.left + obj2.width &&
            obj1.left + obj1.width > obj2.left &&
            obj1.top < obj2.top + obj2.height &&
            obj1.top + obj1.height > obj2.top
        );
    },
    q(selector) {
        return document.querySelector(selector);
    },
    qa(selector) {
        return document.querySelectorAll(selector);
    },
    show_screens(screen) {
        for (let sc of H.qa(".screen")) {
            H.hide(sc)
        }
        if (typeof screen === "string" || screen instanceof String) {
            H.show(screen)
        } else if (Array.isArray(screen)) {
            for (let sc of screen) {
                H.show(sc)
            }
        }
    },
    hide(selector) {
        document.querySelector(selector).style.display = "none"
    },
    show(selector, display = 'block') {
        document.querySelector(selector).style.display = display
    },
    randInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min + 1)) + min;
    },
    async play(sound, currentTime = -1) {
        if (currentTime >= 0) {
            Game.sounds[sound].currentTime = currentTime
        }
        try{
            await Game.sounds[sound].play()
        }
        catch (err){

        }

    },
    show_value(top,left,text,className,layer="#explosions",time=2){
        let value_obj = document.createElement("div")
        value_obj.style.left=left +"px";
        value_obj.style.top=top + "px"
        value_obj.innerText = text
        value_obj.classList.add(className)
        H.q(layer).append(value_obj)
        setTimeout(()=>{
            value_obj.remove()
        },time*1000)
    }

};

/** 游戏角色集合类 */
class RoleCollection {
    constructor() {
        this.roles = [];
    }

    * [Symbol.iterator]() {
        for (let role of this.roles) {
            yield role;
        }
    }

    append(role) {
        this.roles.push(role);
    }

    filter(func) {
        return this.roles.filter(func)
    }

    move() {
        for (let role of this.roles) {
            role.move();
        }
    }

    destroyDoneRoles() {
        for (let role of this.roles) {
            if (role.to_be_destroyed) {
                // 如果需要被摧毁
                if (role.el) {
                    //删除 HTML 元素，如果有的话
                    role.el.remove();
                }
            }
        }
        this.roles = this.roles.filter(r => !r.to_be_destroyed);
    }
}

class Role {
    constructor(
        width,
        height,
        left,
        top,
        speed,
        append_to,
        class_name,
        id_name
    ) {
        this.width = width;
        this.height = height;
        this.speed = speed;
        this.el = null;
        this.append_to = append_to;
        this.class_name = class_name;
        this.id_name = id_name;
        this.to_be_destroyed = false;
        this.init_pos(left, top);
    }

    init_pos(left, top) {
        this.left = left;
        this.top = top;
        this.draw();
    }

    infield() {
        return this.top > (0 - this.height)
            && this.top < Field.height
            && this.left > (0 - this.width)
            && this.left < Field.width
    }

    shotBy() {
        let shotBy = null
        for (let missile of Game.hero_missiles) {
            if (H.collided(missile, this)) {
                shotBy = missile
                break
            }
        }
        return shotBy
    }

    draw() {
        if (!this.el) {
            let el = null;
            if (this.id_name) {
                el = document.getElementById(this.id_name);
                if (!el) {
                    el = document.createElement("div");
                    el.id = this.id_name;
                    if (this.class_name) {
                        el.className = this.class_name;
                    }
                }
            } else if (this.class_name) {
                el = document.createElement("div");
                el.className = this.class_name;
                if (this.id_name) {
                    el.id = this.id_name;
                }
            }
            if (this.append_to) {
                let p = H.q("#" + this.append_to);
                if (!p) {
                    let msg = `ID 为 ${this.append_to} 的页面元素丢失！`;
                    alert(msg);
                    throw msg;
                }
                document.getElementById(this.append_to).append(el);
            }
            this.el = el;
        }
        // 定位到页面
        this.el.style.left = this.left + "px";
        this.el.style.top = this.top + "px";
    }
}

/** 玩家战机类 */
class Hero extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed,
        append_to = "battle-space",
        class_name = "hero",
        id_name = "hero"
    ) {
        super(width, height, left, top, speed, append_to, class_name, id_name);
        this.moving = false;
        this.dir = "";
        this.shooting = false;
        this.life_value = 100;
        this.missile_count = Game.cfg.HERO_LOAD_CAPACITY;
        this.score = Game.cfg.INIT_SCORE;
    }

    move() {
        // 上下左右移动
        if (this.moving) {
            if (this.dir == "left" && !(this.left <= 0)) {
                this.left -= this.speed;
            } else if (
                this.dir == "right" &&
                !(this.left >= Field.width - this.width)
            ) {
                this.left += this.speed;
            } else if (this.dir == "up" && !(this.top <= 0)) {
                this.top -= this.speed;
            } else if (
                this.dir == "down" &&
                !(this.top + this.height >= Field.height)
            ) {
                this.top += this.speed;
            }

            this.draw();
        }
        // 与敌机相撞？
        for (let enemy of Game.enemies) {
            if (this.collided_by(enemy)) {
                this.el.style.animation = null;
                this.el.offsetHeight;
                this.el.style.animation = "hero-blink 0.2s 2";

                on_score_change.detail.value = Game.cfg.DESTROY_ENEMY_GAIN_S;
                document.dispatchEvent(on_score_change);
                // Or: this.tune_score(+Game.cfg.HIT_ENEMY_GAIN);

                on_life_value_change.detail.value = -Game.cfg.COLLIDED_BY_ENEMY_COST_H;
                document.dispatchEvent(on_life_value_change);
                // Or: this.tune_life_value(-Game.cfg.COLLIDED_BY_ENEMY_COST_H);

                explodes_at.push({
                    left: enemy.left,
                    top: enemy.top
                });
                H.show_value(enemy.top+20,enemy.left+20,"+"+Game.cfg.DESTROY_ENEMY_GAIN_S,"gain")
                H.show_value(enemy.top+40,enemy.left+40,"-"+Game.cfg.COLLIDED_BY_ENEMY_COST_H+"%","loss")
                enemy.init_pos(...H.get_rand_drop_pos(enemy.width));
            }
        }
        // 接住能量筒？
        for (let can of Game.ENERGY_CAN_COUNT_MAX) {
            if (this.caught_energy_can(can) && this.life_value < 100) {
                this.tune_life_value(Game.cfg.CATCH_ENERGY_CAN_GAIN_H);
                H.show_value(this.top+10,this.left+10,"+"+Game.cfg.CATCH_ENERGY_CAN_GAIN_H+"%","gain")
                can.init_pos(...H.get_rand_drop_pos(can.width));
                // Game.sounds["charging"].currentTime = 0;
                // Game.sounds["charging"].play();
                H.play("charging",0)
            }
        }
        // 接住弹药箱？
        for (let ammo of Game.AMMO_BOX_COUNT_MAX) {
            if (this.caught_ammo_box(ammo) && this.missile_count < Game.cfg.HERO_LOAD_CAPACITY) {
                this.tune_missile_count(Game.cfg.CATCH_AMMO_BOX_GAIN_M);
                H.show_value(this.top+10,this.left+10,"+"+Game.cfg.CATCH_AMMO_BOX_GAIN_M+" Missiles","gain")
                ammo.init_pos(...H.get_rand_drop_pos(ammo.width));
                // Game.sounds["reloading"].currentTime = 0;
                // Game.sounds["reloading"].play();
                H.play("reloading",0)
            }
        }
        // 被敌机导弹击中？
        for (let em of Game.enemy_missiles) {
            if (H.collided(this, em)) {
                this.el.style.animation = null;
                this.el.offsetHeight;
                this.el.style.animation = "hero-blink 0.2s 2";
                this.tune_life_value(-Game.cfg.SHOT_BY_ENEMY_COST_H)
                // Game.sounds["warning"].currentTime = 0
                // Game.sounds["warning"].play()
                H.play("warning",0)
                explodes_at.push({
                    left: this.left - 10,
                    top: this.top - 50
                });
                H.show_value(this.top+10,this.left+10,"-"+Game.cfg.SHOT_BY_ENEMY_COST_H+"%","loss")
                em.left = -1000
                em.top = -1000
                em.to_be_reloaded = true

            }
        }
    }

    /** 开火 */
    fire() {
        if (!this.shooting && this.missile_count > 0) {
            Game.hero_missiles.append(
                new HeroMissile(10, 28, this.left + 20, this.top - 20)
            );
            this.missile_count--;
            this.tune_missile_count(-1);
            // Game.sounds["launch"].currentTime = 0;
            // Game.sounds["launch"].play();
            H.play("launch",0)
            this.shooting = true;
        }
    }

    collided_by(enemy) {
        return H.collided(this, enemy);
    }

    caught_energy_can(energy_can) {
        return H.collided(this, energy_can);
    }

    caught_ammo_box(ammo) {
        return H.collided(this, ammo);
    }

    tune_life_value(val) {
        this.life_value += val;
        if (this.life_value > 100) {
            this.life_value = 100;
        }
        if (this.life_value <= 0) {
            this.life_value = 0;

            cancelAnimationFrame(Game.intv);
            Game.explodeAt(Game.hero.left, Game.hero.top)
            Game.hero.el.style.display = "none"
            setTimeout(() => {
                H.play("explosion",0)
                Game.over();

            }, 1000)


        }
        H.q("#life-indicator").value = this.life_value;
        H.q("#life-indicator-text").innerText =
            this.life_value + "%";

        if (this.life_value >= 80) {
            H.q("#hero").style.backgroundImage = "url('images/hero.png')"
        } else if (this.life_value >= 30) {

            H.q("#hero").style.backgroundImage = "url('images/hero-h1.png')"
        } else {
            H.q("#hero").style.backgroundImage = "url('images/hero-h2.png')"
        }
    }

    tune_missile_count(count) {
        this.missile_count += count;
        if (this.missile_count > Game.cfg.HERO_LOAD_CAPACITY) {
            this.missile_count = Game.cfg.HERO_LOAD_CAPACITY;
        }
        if (this.missile_count < 0) {
            this.missile_count = 0;
        }
        H.q("#missile-count").innerText = this.missile_count;
    }

    tune_score(value) {
        this.score += value;
        H.q("#score").innerText =
            this.score < 0 ? 0 : this.score;
        if (this.score <= 0) {
            this.score = 0;
            Game.over();
        }
    }
}

/** 敌军战机类 */
class Enemy extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed,
        append_to = "enemies",
        class_name = "enemy",
        id_name = null
    ) {
        if (left === null && top === null) {
            [left, top] = H.get_rand_drop_pos(width);
        }
        super(width, height, left, top, speed, append_to, class_name, id_name);
    }

    move() {
        if (this.top < Field.height) {
            this.top += this.speed;
            // 被导弹击中？
            for (let missile of Game.hero_missiles) {
                if (this.hit_by(missile)) {
                    this.init_pos(...H.get_rand_drop_pos(this.width));
                    missile.to_be_destroyed = true;
                    Game.hero.tune_score(Game.cfg.DESTROY_ENEMY_GAIN_S);
                    explodes_at.push({
                        left: missile.left - 35,
                        top: missile.top - 65
                    });
                    H.show_value(missile.top-30,missile.left-10,"+"+Game.cfg.DESTROY_ENEMY_GAIN_S,"gain")


                }
            }
            this.draw();
        } else {
            H.show_value(Field.height-30,this.left+10,"-"+Game.cfg.ESCAPING_OF_ENEMY_COST_S,'loss')
            Game.hero.tune_score(-Game.cfg.ESCAPING_OF_ENEMY_COST_S);
            this.init_pos(...H.get_rand_drop_pos(this.width));
        }
    }

    hit_by(missile) {
        return H.collided(this, missile);
    }
}

/** 能量筒类 */
class EnergyCan extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed,
        append_to = "energy-cans",
        class_name = "energy-can",
        id_name = null
    ) {
        if (left === null && top === null) {
            [left, top] = H.get_rand_drop_pos(width);
        }
        super(width, height, left, top, speed, append_to, class_name, id_name);
    }

    move() {
        let missile = this.shotBy()
        if (missile) {
            setTimeout(() => {
                H.play("gasp",0)
            }, 300)
            Game.hero.tune_score(-Game.cfg.DESTROY_ENERGY_CAN_COST_S)
            missile.to_be_destroyed = true;
            explodes_at.push({
                left: missile.left - 35,
                top: missile.top - 65
            });
            H.show_value(this.top+10,this.left+10,"-"+Game.cfg.DESTROY_ENERGY_CAN_COST_S,'loss')
            this.init_pos(...H.get_rand_drop_pos(this.width));
        } else if (this.top > Field.height) {
            this.init_pos(...H.get_rand_drop_pos(this.width));
        } else {
            this.top += this.speed;
        }
        this.draw();
    }
}

/** 弹药箱类 */
class AmmoBox extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed,
        append_to = "ammo-boxes",
        class_name = "ammo-box",
        id_name = null
    ) {
        if (left === null && top === null) {
            [left, top] = H.get_rand_drop_pos(width);
        }
        super(width, height, left, top, speed, append_to, class_name, id_name);
    }

    move() {
        let missile = this.shotBy()
        if (missile) {
            setTimeout(() => {
                H.play("gasp",0)
            }, 300)

            missile.to_be_destroyed = true;
            explodes_at.push({
                left: missile.left - 35,
                top: missile.top - 65
            });
            Game.hero.tune_score(-Game.cfg.DESTROY_AMMO_BOX_COST_S)
            H.show_value(this.top+10,this.left+10,"-"+Game.cfg.DESTROY_AMMO_BOX_COST_S,'loss')
            this.init_pos(...H.get_rand_drop_pos(this.width));
        } else if (this.top > Field.height) {
            this.init_pos(...H.get_rand_drop_pos(this.width));
        } else {
            this.top += this.speed;
        }
        this.draw();
    }
}

/** 玩家战机导弹类 */
class HeroMissile extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed = 8,
        append_to = "hero-missiles",
        class_name = "hero-missile",
        id_name = null
    ) {
        super(width, height, left, top, speed, append_to, class_name, id_name);
        this.done = false;
    }

    move() {
        if (this.top >= 0 - this.height) {
            this.top -= this.speed;
            this.draw();
        } else {
            this.to_be_destroyed = true;
        }
    }
}

class EnemyMissile extends Role {
    constructor(
        width,
        height,
        left,
        top,
        speed = 8,
        append_to = "enemy-missiles",
        class_name = "enemy-missile",
        id_name = null,
        to_be_reloaded = true
    ) {
        super(width, height, left, top, speed, append_to, class_name, id_name);
        this.done = false;
        this.to_be_reloaded = to_be_reloaded
    }

    reloadedTo(enemy) {
        this.top = enemy.top + enemy.height
        this.left = enemy.left + enemy.width / 2 - this.width / 2
    }

    move() {
        if (this.top <= Field.height) {
            this.top += this.speed;
            this.draw();

        } else {
            this.to_be_reloaded = true;
        }
    }

}

/** 游戏类 */
let Game = {
    cfg: config.easy,
    difficulty: "EASY",
    sounds: {},
    sounds_loaded: 0,
    intv: null,
    ready: false,
    current_user: "",
    hero: null,
    enemies: null,
    ENERGY_CAN_COUNT_MAX: null,
    AMMO_BOX_COUNT_MAX: null,
    hero_missiles: null,
    enemy_missiles: null,
    enemy_missile_delay: 0,
    coming_enemies: 0,
    coming_missiles: 0,
    coming_ammos: 0,
    coming_cans: 0,
    paused: false,
    running: false,
    is_over: false,
    cur_screen: "start",
    init() {

        H.q("#hero").style.backgroundImage = "url('images/hero.png')"
        Game.id = Date.now()
        Game.paused = false;
        Game.is_over = false;
        Game.enemy_last_shot_at = Date.now();
        Game.hero = new Hero(50, 50, 575, 745, 5);
        Game.init_info();
        Game.cur_screen = "start"
        H.show("#start", "flex")
        H.hide("#battle-space")
        H.hide("#game-over")
        // H.hide("#start")
        // H.show("#battle-space")
        // H.show("#game-over")
        for (let el of H.qa(".need-erase")) {
            el.innerHTML = "";
        }
        H.q("#clear-data").disabled =
            localStorage.getItem(config.GAME_DATA_KEY) === null;

        Game.hero_missiles = new RoleCollection();

        Game.enemies = new RoleCollection();
        for (let i = 0; i < Game.cfg.ENEMY_COUNT_MAX; i++) {
            Game.enemies.append(new Enemy(50, 60, null, null, 3));
        }
        Game.ENERGY_CAN_COUNT_MAX = new RoleCollection();

        for (let i = 0; i < Game.cfg.ENERGY_CAN_COUNT_MAX; i++) {
            Game.ENERGY_CAN_COUNT_MAX.append(new EnergyCan(20, 32, null, null, 4));
        }
        Game.AMMO_BOX_COUNT_MAX = new RoleCollection();

        for (let i = 0; i < Game.cfg.ENERGY_CAN_COUNT_MAX; i++) {
            Game.AMMO_BOX_COUNT_MAX.append(new AmmoBox(40, 21, null, null, 4));
        }
        Game.enemy_missiles = new RoleCollection()
        for (let i = 0; i < Game.cfg.ENEMY_MISSILE_COUNT_MAX; i++) {
            Game.enemy_missiles.append(new EnemyMissile(10, 28, -1000, -1000))
        }

        let sound_names = [
            "background",
            "launch",
            "explosion",
            "charging",
            "reloading",
            "gasp",
            "warning"
        ];
        for (let sound_name of sound_names) {
            let sound = new Audio();

            sound.addEventListener("canplaythrough", function (e) {
                Game.sounds[sound_name] = sound;
                Game.sounds_loaded++;
                if (sound_name === "background") {
                    sound.loop = true;
                    sound.ontimeupdate = async function (i) {
                        if ((this.currentTime / this.duration) > 32.5 / 34) {
                            this.currentTime = 0;
                            try{
                                await this.play();
                            }
                            catch(err){

                            }

                        }
                    };
                }
                if (Game.sounds_loaded === sound_names.length) {
                    Game.ready = true;
                }
            });
            sound.src = `sound/${sound_name}.mp3`;
        }
        let instruction = `
				<li>1. Destorying an enemy fighter <img src="images/enemy.png" style="width: 30px;height: auto"> gets ${
            Game.cfg.DESTROY_ENEMY_GAIN_S
        } points of score；</li>
				<li>2. For each enemy fighter <img src="images/enemy.png" style="width: 30px;height: auto"> escaping,  ${
            Game.cfg.ESCAPING_OF_ENEMY_COST_S
        } point(s) of score is/are lost；</li>
				<li>3. Colliding with an enemy fightre <img src="images/enemy.png" style="width: 30px;height: auto"> gets ${
            Game.cfg.DESTROY_ENEMY_GAIN_S
        } points of score and ${Game.cfg.COLLIDED_BY_ENEMY_COST_H}% of healthness is lost；</li>
				<li>4. Catching an energy can <img src="images/energy-can.png" style="width: 20px;height: auto"> regains ${
            Game.cfg.CATCH_ENERGY_CAN_GAIN_M
        }% of healthness；</li>
				<li>5. Loading capacity is 50. Catching an ammo box <img src="images/ammo-box.png" style="height: 20px;width: auto"> gets ${
            Game.cfg.CATCH_AMMO_BOX_GAIN_M
        } missiles；</li>
				<li>6. Destroying an energy can <img src="images/energy-can.png" style="width: 20px;height: auto"> loses ${Game.cfg.DESTROY_ENERGY_CAN_COST_S} point(s) and destroying an ammo box <img src="images/ammo-box.png" style="height: 20px;width: auto">, loses 
					${Game.cfg.DESTROY_AMMO_BOX_COST_S} point(s)；</li>
					<li>7. Shot by enemy missile, ${Game.cfg.SHOT_BY_ENEMY_COST_H}% of healthness is lost;</li>
					<li>8. When score is 0 or healthness is 0%, game over。</li>
					`;
        document.getElementById("instruction").innerHTML = instruction;

        // 动态更换背景
        let bkgno = Math.floor(1 + Math.random() * (6 - 1 + 1));
        H.q("#battle-space").style.backgroundImage =
            "url(images/background" + bkgno + ".jpg)";
        document.getElementById("start").style.backgroundImage =
            "url(images/background" + bkgno + ".jpg)";
    },

    init_info() {
        H.q("#score").innerText = Game.cfg.INIT_SCORE;
        H.q("#missile-count").innerText = Game.cfg.HERO_LOAD_CAPACITY;
        H.q("#difficulty-text").innerText = Game.difficulty;
        H.q("#life-indicator").value = Game.hero.life_value;
        H.q("#life-indicator-text").innerText = Game.hero.life_value + "%";
        H.q("#player").innerText = Game.current_user
        H.q("#enemy-count-max").innerText = Game.cfg.ENEMY_COUNT_MAX
        H.q("#enemy-missile-count-max").innerText = Game.cfg.ENEMY_MISSILE_COUNT_MAX
        H.q("#energy-can-count-max").innerText = Game.cfg.ENERGY_CAN_COUNT_MAX
        H.q("#ammo-box-count-max").innerText = Game.cfg.AMMO_BOX_COUNT_MAX
        H.q("#hero-load-capacity").innerText = Game.cfg.HERO_LOAD_CAPACITY
    },

    async switch_sound(want_to,init=false) {
        if (want_to === "on") {
            for (let sound in Game.sounds) {
                if (Game.sounds.hasOwnProperty(sound)) {
                    if (sound === "background") {
                        H.play(sound)
                    } else if(sound.currentTime>0){
                        H.play(sound)
                    }
                }
            }
        } else {
            for (let sound in Game.sounds) {
                if (Game.sounds.hasOwnProperty(sound)) {
                    Game.sounds[sound].pause();
                }
            }
        }
    },

    explodeAt(x, y) {
        let exp = document.createElement("div")
        exp.style.left = x + "px"
        exp.style.top = y + "px"
        exp.style.position = "absolute"
        exp.classList.add("explode")
        document.querySelector("#explosions").append(exp)
        exp.addEventListener("animationend", (e) => {
            e.target.remove()
        })
    },

    start() {
        Game.hero.el.style.animation = null
        Game.hero.el.style.display = "block"
        Game.hero.draw();

        Game.switch_sound("on")

        let fps = 60;
        let now;
        let then = Date.now();
        let interval = 1000 / fps;
        let delta;

        function loop() {
            if (Game.cur_screen === "battle-space") {
                document.querySelectorAll("*").forEach((el) => {
                    el.style.animationPlayState = "running"
                })

                Game.intv = requestAnimationFrame(loop);
                now = Date.now();
                delta = now - then;

                if (delta > interval) {

                    then = now - (delta % interval);

                    // ... Below are Code for Drawing the Frame ...

                    for (let m of Game.enemy_missiles.filter(em => em.to_be_reloaded === true)) {
                        let now = Date.now()
                        if (now >= Game.enemy_last_shot_at + Game.enemy_missile_delay) {
                            m.to_be_reloaded = false
                            let enemies = Game.enemies.filter(e => e.top >= 0 - e.height && e.top <= Field.height - e.height)
                            if (enemies.length > 0) {
                                let enemy = enemies[H.randInt(0, enemies.length - 1)]
                                m.reloadedTo(enemy)
                                Game.enemy_missile_delay = H.randInt(50, 2000)
                                Game.enemy_last_shot_at = Date.now()
                            }

                        }
                    }
                    Game.hero.move();
                    Game.enemies.move();
                    Game.ENERGY_CAN_COUNT_MAX.move();
                    Game.AMMO_BOX_COUNT_MAX.move();
                    Game.hero_missiles.move();
                    Game.enemy_missiles.move();
                    Game.hero_missiles.destroyDoneRoles();
                    drawExplosion();

                    H.q("#coming-enemy-count").textContent = Game.enemies.filter((e) => e.infield()).length
                    H.q("#coming-enemy-missile-count").textContent = Game.enemy_missiles.filter((e) => e.infield()).length
                    H.q("#coming-ammo-count").textContent = Game.AMMO_BOX_COUNT_MAX.filter((e) => e.infield()).length
                    H.q("#coming-can-count").textContent = Game.ENERGY_CAN_COUNT_MAX.filter((e) => e.infield()).length


                }
            } else {
                document.querySelectorAll("*").forEach((el) => {
                    el.style.animationPlayState = "paused"
                })
                Game.switch_sound("off")
                if (Game.is_over) {
                    cancelAnimationFrame(Game.intv);
                }

            }
        }

        loop();
    },

    over() {
        Game.is_over = true;
        Game.cur_screen = "game-over"

        Game.switch_sound("off")
        document.querySelectorAll("*").forEach((el) => {
            el.style.animationPlayState = "paused"
        })
        document.getElementById("game-over").style.display = "flex";
        let data = JSON.parse(localStorage.getItem(config.GAME_DATA_KEY)) || [];
        data.unshift({
            id: Game.id,
            player: Game.current_user,
            difficulty: Game.difficulty,
            score: Game.hero.score
        });
        data = data.filter(e => e.difficulty == Game.difficulty);
        data.sort((a, b) => (a.score > b.score ? -1 : 1));
        document.getElementById("rank").innerHTML = "";
        for (let i = 0; i < data.length; i++) {
            let self_found =
                data[i].id == Game.id
            let hlight = self_found ? "hlight" : "";
            if (i < 3 || (i >= 3 && hlight)) {
                document.getElementById("rank").innerHTML += `
				<tr class=${hlight}>
					<td>${i + 1}</td>
					<td>${data[i].player}</td>
					<td>${data[i].difficulty}</td>
					<td>${data[i].score}</td>
				</tr>
				`;
            }
            if (self_found && i >= 3) {
                break;
            }
        }
        localStorage.setItem(config.GAME_DATA_KEY, JSON.stringify(data));
    }
};

Game.init();
//})();
